﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;
#if !DOT_NET
using Windows.Storage;
using Windows.Storage.Streams;

#endif
#if !WINDOWS_PHONE
using System.IO.Compression;

#endif

namespace StyleMVVM.Utilities
{
	/// <summary>
	/// This class contains generic methods to serialize and deserialize objects to files
	/// </summary>
	public class SerializationHelper
	{
#if DOT_NET
		public static Task SerializeToFile<T>(string fileName,
		                                      T value,
		                                      CreationCollisionOption options = CreationCollisionOption.ReplaceExisting,
		                                      IEnumerable<Type> knownTypes = null) where T : class
		{
			return Task.Run(
				() =>
					{
						using (MemoryStream ms = new MemoryStream())
						{
							SerializeToStream(ms, value, knownTypes);

							FileMode fileMode = FileMode.Create;

							switch (options)
							{
								case CreationCollisionOption.FailIfExists:
									fileMode = FileMode.CreateNew;
									break;

								case CreationCollisionOption.GenerateUniqueName:
									fileMode = FileMode.CreateNew;

									int i = 1;
									string extension = null;

									while (File.Exists(fileName + extension))
									{
										extension = i.ToString();
										i++;
									}

									fileName = fileName + extension;

									break;

								case CreationCollisionOption.OpenIfExists:
								case CreationCollisionOption.ReplaceExisting:
									fileMode = FileMode.Create;
									break;
							}

							using (FileStream fileStream = File.Open(fileName, fileMode))
							{
								byte[] serialized = ms.ToArray();

								fileStream.Write(serialized, 0, serialized.Length);
							}
						}
					});
		}
#else

		public static async Task SerializeToFile<T>(string fileName,
		                                            T value,
		                                            CreationCollisionOption options = CreationCollisionOption.ReplaceExisting,
		                                            IEnumerable<Type> knownTypes = null) where T : class
		{
			using (MemoryStream ms = new MemoryStream())
			{
				SerializeToStream(ms, value, knownTypes);

				StorageFile file =
					await ApplicationData.Current.LocalFolder.CreateFileAsync(fileName, options);

				using (Stream fileStream = await file.OpenStreamForWriteAsync())
				{
					ms.Seek(0, SeekOrigin.Begin);

					await ms.CopyToAsync(fileStream);
					await fileStream.FlushAsync();
				}
			}
		}
#endif

#if DOT_NET
		public static Task<T> DeserializeFromFile<T>(string fileName, IEnumerable<Type> knownTypes = null) where T : class
		{
			return Task.Run(
				() =>
					{
						using (FileStream fileStream = File.OpenRead(fileName))
						{
							return DeserializeFromStream<T>(fileStream, knownTypes);
						}
					});
		}
#else
		public static async Task<T> DeserializeFromFile<T>(string fileName, IEnumerable<Type> knownTypes = null)
			where T : class
		{
			StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(fileName);

			using (IInputStream inStream = await file.OpenSequentialReadAsync())
			{
				return DeserializeFromStream<T>(inStream.AsStreamForRead(), knownTypes);
			}
		}
#endif

#if !WINDOWS_PHONE

#if DOT_NET
		public static Task CompressToFile<T>(string fileName,
		                                     T value,
		                                     CreationCollisionOption options = CreationCollisionOption.ReplaceExisting,
		                                     IEnumerable<Type> knownTypes = null) where T : class
		{
			return Task.Run(
				() =>
					{
						FileMode fileMode = FileMode.Create;

						switch (options)
						{
							case CreationCollisionOption.FailIfExists:
								fileMode = FileMode.CreateNew;
								break;

							case CreationCollisionOption.GenerateUniqueName:
								fileMode = FileMode.CreateNew;

								int i = 1;
								string extension = null;

								while (File.Exists(fileName + extension))
								{
									extension = i.ToString();
									i++;
								}

								fileName = fileName + extension;

								break;

							case CreationCollisionOption.OpenIfExists:
							case CreationCollisionOption.ReplaceExisting:
								fileMode = FileMode.Create;
								break;
						}

						using (FileStream fileStream = File.Open(fileName, fileMode))
						{
							using (GZipStream zippedStream = new GZipStream(fileStream, CompressionLevel.Fastest, false))
							{
								SerializeToStream(zippedStream, value, knownTypes);
							}
						}
					});
		}
#else
		public static async Task CompressToFile<T>(string fileName,
		                                           T value,
		                                           CreationCollisionOption options = CreationCollisionOption.ReplaceExisting,
		                                           IEnumerable<Type> knownTypes = null) where T : class
		{
			StorageFile file =
				await ApplicationData.Current.LocalFolder.CreateFileAsync(fileName, options);

			using (Stream fileStream = await file.OpenStreamForWriteAsync())
			{
				using (GZipStream zippedStream = new GZipStream(fileStream, CompressionLevel.Fastest, false))
				{
					SerializeToStream(zippedStream, value, knownTypes);
				}
			}
		}
#endif

#if DOT_NET
		public static Task<T> DecompressFromFile<T>(string fileName, IEnumerable<Type> knownTypes = null)
			where T : class
		{
			return Task.Run(() =>
				                {
					                using (FileStream fileStream = File.OpenRead(fileName))
					                {
						                using (GZipStream zipStream = new GZipStream(fileStream, CompressionMode.Decompress, false))
						                {
							                return DeserializeFromStream<T>(zipStream, knownTypes);
						                }
					                }
				                });
		}
#else
		public static async Task<T> DecompressFromFile<T>(string fileName, IEnumerable<Type> knownTypes = null)
			where T : class
		{
			StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(fileName);

			using (IInputStream inStream = await file.OpenSequentialReadAsync())
			{
				using (GZipStream zipStream = new GZipStream(inStream.AsStreamForRead(), CompressionMode.Decompress, false))
				{
					return DeserializeFromStream<T>(zipStream, knownTypes);
				}
			}
		}
#endif

#endif

		public static void SerializeToStream<T>(Stream outStream, T value, IEnumerable<Type> knownTypes = null)
			where T : class
		{
			DataContractJsonSerializer contractSerializer = null;

			if (knownTypes != null)
			{
				contractSerializer = new DataContractJsonSerializer(typeof(T), knownTypes);
			}
			else
			{
				contractSerializer = new DataContractJsonSerializer(typeof(T));
			}

			contractSerializer.WriteObject(outStream, value);
		}

		public static T DeserializeFromStream<T>(Stream inStream, IEnumerable<Type> knownTypes = null) where T : class
		{
			DataContractJsonSerializer contractSerializer = null;

			if (knownTypes != null)
			{
				contractSerializer = new DataContractJsonSerializer(typeof(T), knownTypes);
			}
			else
			{
				contractSerializer = new DataContractJsonSerializer(typeof(T));
			}

			return contractSerializer.ReadObject(inStream) as T;
		}
	}
}